﻿// ———————————————————————–
// <copyright file="CoTWrapper.cs" company="The MITRE Corporation">
//    Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
// </copyright>
// ———————————————————————–
/////////////////////////////////////////////////////////////////////////////////////////////////
// IPAWSProxy.cs - Wrapper for IPAWS to implement EDXL Messaging with IPAWS
// Project: EDXLLib-EDXLIPAWS
//
// Language:    C#, .NET 4.0
// Platform:    Windows 7, VS 2010
// Author:      Brian Wilkins The MITRE Corporation
/////////////////////////////////////////////////////////////////////////////////////////////////

// Copyright (c) 2010 The MITRE Corporation. All rights reserved.
//
// NOTICE
// This software was produced for the U. S. Government
// under Contract No. FA8721-09-C-0001, and is
// subject to the Rights in Noncommercial Computer Software
// and Noncommercial Computer Software Documentation Clause
// (DFARS) 252.227-7014 (JUN 1995)
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Configuration;
using System.ServiceModel.Security;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Serialization;
using EDXLSharp;
using EDXLSharp.EDXLDELib;
using IPAWSProxyLib.IPAWS_EDXL_Svc;
using log4net;
using log4net.Appender;
using log4net.Layout;

namespace IPAWSProxyLib
{
  /// <summary>
  /// This class serves as the main connection point to the IPAWS EDXL SOAP Services.
  /// </summary>
  public class EDXLProxy
  {
    #region Public Delegates/Events

    #endregion

    #region Private Member Variables

    /// <summary>
    /// Default Cog User
    /// </summary>
    private const string COGUSER = "DMOPEN";

    /// <summary>
    /// Default Cog Id (for testing)
    /// </summary>
    private const int COGID = 999;

    /// <summary>
    /// Current IPAWS Request API 
    /// </summary>
    private const string REQUESTAPI = "REQUEST1";

    /// <summary>
    /// IPAWS getACK Operation Function
    /// </summary>
    private const string REQUESTPING = "getACK";

    /// <summary>
    /// IPAWS getMessageList operation
    /// </summary>
    private const string REQUESTMSGLIST = "getMessageList";

    /// <summary>
    /// IPAWS getMessage operation
    /// </summary>
    private const string REQUESTMSG = "getMessage";

    /// <summary>
    /// IPAWS client connection
    /// </summary>
    private DMOPEN_EDXLDEServiceClient mEDXLClient;
    /// <summary>
    /// IPAWS EDXL header, required for all messages to IPAWS
    /// </summary>
    private EdxlHeaderTypeDef mEDXLHeader;

    /// <summary>
    /// Cog User for this client session
    /// </summary>
    private string mCogUser;

    /// <summary>
    /// Cog Id for this client session
    /// </summary>
    private int mCogId;

    /// <summary>
    /// Error logger
    /// </summary>
    private static ILog log = Donuts.Core.Logging.Logger.GetLogger("Application");

    #endregion

    #region Constructors

    /// <summary>
    /// Initializes a new instance of the IPAWSProxy class.  Default Constructor, uses default CogId and CogUser
    /// </summary>
    public EDXLProxy()
    {
      this.mCogId = COGID;
      this.mCogUser = COGUSER;

      try
      {
        this.InitializeClient();
      }
      catch (Exception Ex)
      {
        log.Error("Error creating EDXLProxy: " + Ex.ToString());
        throw Ex;
      }

      this.CreateEDXLHeader();
    }

    /// <summary>
    /// Initializes a new instance of the IPAWSProxy class.  Cog Constructor, uses passed in CogId and CogUser
    /// </summary>
    /// <param name="CogId">IPAWS Cog Id</param>
    /// <param name="CogUser">IPAWS Cog User</param>
    public EDXLProxy(int CogId, string CogUser)
    {
      this.mCogId = CogId;
      this.mCogUser = CogUser;

      try
      {
        this.InitializeClient();
      }
      catch (Exception Ex)
      {
        log.Error("Error creating EDXLProxy: " + Ex.ToString());
        throw Ex;
      }

      this.CreateEDXLHeader();
    }
    #endregion

    #region Public Accessors

    #endregion

    #region Public Member Functions

    /// <summary>
    /// Ping makes a "ping" request of IPAWS expects Pong in return.  
    /// This method will throw an exception if one is caught.
    /// </summary>
    public void Ping()
    {
      parameterListItem pListItem = new parameterListItem();
      pListItem.parameterName = "ping";

      parameterListItem[] pListItems = new parameterListItem[] { pListItem };

      string responseOperation = String.Empty;
      string responseType = String.Empty;

      try
      {
        parameterListItem1[] returnParams = mEDXLClient.getRequest(mEDXLHeader, REQUESTAPI, REQUESTPING, pListItems, out responseOperation, out responseType);

        if (returnParams != null)
        {
          if (returnParams[0].parameterValue.Equals("PONG"))
          {
            return;
          }
          else
          {
            log.Error("Unexpected result returned in Ping.  Expecting 'PONG'.");
            throw new System.Exception("Unexpected result returned.  Expecting 'PONG'.");
          }
        }
        else
        {
          log.Error("Unexpected result returned in Ping.  Return parameters were NULL.");

          throw new System.NullReferenceException("Unexpected result returned.  Return parameters were NULL.");
        }
      }
      catch (Exception Ex)
      {
        log.Error("Unexpected error occurred in Ping.  Error = " + Ex.ToString());
        throw Ex;
      }
    }

    /// <summary>
    /// TryPing makes a "ping" request of IPAWS expects Pong in return.  
    /// This method will not throw an exception.
    /// </summary>
    /// <returns>Whether not Ping was successful</returns>
    public bool TryPing()
    {
      bool bGotPong = true;

      try
      {
        Ping();
      }
      catch
      {
        bGotPong = false;
      }

      return bGotPong;
    }

    /// <summary>
    /// GetMessageList returns a list of name value collections which represents the various field and values in the EDXL Distribution
    /// messages that satisfy the passed in Search parameters.  Each name value collection represents a different EDXL Distribution 
    /// message.  The fields include distributionId, senderId, etc... from the EDXL Distribution element.  
    /// </summary>
    /// <param name="SearchName">Name of the field to search, such as distributionId.  Valid fields can be found in the 
    /// IPAWS-OPEN-v2 Interface Design Guidance 1-2 document</param>
    /// <param name="SearchValue">Value to search for, such as TestMessage01</param>
    /// <param name="ComparisonOperation">Comparison to use against the search value, such as equalto</param>
    /// <returns>List of name value collections representing the found messages</returns>
    public List<NameValueCollection> GetMessageList(string SearchName, string SearchValue, string ComparisonOperation)
    {
      parameterListItem pListItem = new parameterListItem();
      pListItem.parameterName = SearchName;
      pListItem.comparisonOp = ComparisonOperation;
      pListItem.parameterValue = new string[] { SearchValue };

      parameterListItem[] pListItems = new parameterListItem[] { pListItem };

      string responseOperation = String.Empty;
      string responseType = String.Empty;

      List<NameValueCollection> returnList;

      try
      {
        parameterListItem1[] paramList = mEDXLClient.getRequest(mEDXLHeader, REQUESTAPI, REQUESTMSGLIST, pListItems, out responseOperation, out responseType);

        returnList = new List<NameValueCollection>();

        foreach (parameterListItem1 param in paramList)
        {
          NameValueCollection item = new NameValueCollection();

          item.Add(param.parameterName, param.parameterValue);

          foreach (subParameterListItem subParam in param.subParaListItem)
          {
            item.Add(subParam.subParameterName, subParam.subParameterValue);
          }

          returnList.Add(item);
        }

        return returnList;
      }
      catch (Exception Ex)
      {
        log.Error("Error getting message list: " + Ex.ToString());
        throw;
      }
    }

    /// <summary>
    /// TryGetMessageList returns whether or not a GetMessages request succeeded.  
    /// </summary>
    /// <param name="SearchName">Name of the field to search, such as distributionId.  Valid fields can be found in the 
    /// IPAWS-OPEN-v2 Interface Design Guidance 1-2 document</param>
    /// <param name="SearchValue">Value to search for, such as TestMessage01</param>
    /// <param name="ComparisonOperation">Comparison to use against the search value, such as equalto</param>
    /// <param name="MessageList">A list of name value collections which represents the various field and values in the EDXL Distribution
    /// messages that satisfy the passed in Search parameters.  Each name value collection represents a different EDXL Distribution 
    /// message.  The fields include distributionId, senderId, etc... from the EDXL Distribution element.</param>
    /// <returns>whether or not a GetMessages request succeeded</returns>
    public bool TryGetMessageList(string SearchName, string SearchValue, string ComparisonOperation, ref List<NameValueCollection> MessageList)
    {
      bool gotMessages = true;

      MessageList = null;

      try
      {
        MessageList = GetMessageList(SearchName, SearchValue, ComparisonOperation);
      }
      catch
      {
        gotMessages = false;
      }

      return gotMessages;
    }

    /// <summary>
    /// GetMessages returns a list of ICNET EDXLDEs that satisfy the passed in DistributionId and
    /// SenderId.  Either the DistributionId or SenderId can be null/empty/whitespace but not both.
    /// </summary>
    /// <param name="SearchName">Name of the field to search, such as distributionId.  Valid fields can be found in the 
    /// IPAWS-OPEN-v2 Interface Design Guidance 1-2 document</param>
    /// <param name="SearchValue">Value to search for, such as TestMessage01</param>
    /// <param name="ComparisonOperation">Comparison to use against the search value, such as equalto</param>
    /// <returns>A list of ICNET EDXLDEs that satisfy the passed in search criteria</returns>
    public List<EDXLDE> GetMessages(string SearchName, string SearchValue, string ComparisonOperation)
    {
      EDXLDistribution[] edxlDists = null;
      List<EDXLDE> edxlDEs = null;

      parameterListItem pListItem = new parameterListItem();
      pListItem.parameterName = SearchName;
      pListItem.comparisonOp = ComparisonOperation;
      pListItem.parameterValue = new string[] { SearchValue };

      parameterListItem[] pListItems = new parameterListItem[] { pListItem };

      try
      {
        edxlDists = mEDXLClient.getMessage(mEDXLHeader, REQUESTAPI, REQUESTMSG, pListItems);
        edxlDEs = new List<EDXLDE>();

        if (edxlDists != null && edxlDists.Length > 0)
        {
          foreach (EDXLDistribution dist in edxlDists)
          {
            try
            {
              EDXLDE tmp = Convert_To_ICNET_EDXL(dist);

              if (tmp != null)
              {
                edxlDEs.Add(tmp);
              }
            }
            catch (Exception Ex)
            {
              log.Error("Error getting messages: " + Ex.ToString());
              throw Ex;
            }
          }
        }

        return edxlDEs;
      }
      catch (Exception Ex)
      {
        log.Error("Error getting messages: " + Ex.ToString());
        throw;
      }
    }

    /// <summary>
    /// TryGetMessages returns whether or not a GetMessages request succeeded.  
    /// </summary>
    /// <param name="SearchName">Name of the field to search, such as distributionId.  Valid fields can be found in the 
    /// IPAWS-OPEN-v2 Interface Design Guidance 1-2 document</param>
    /// <param name="SearchValue">Value to search for, such as TestMessage01</param>
    /// <param name="ComparisonOperation">Comparison to use against the search value, such as equalto</param>
    /// <param name="MessageList">A list of ICNET EDXLDEs that satisfy the passed in search criteria</param>
    /// <returns>Whether or not a GetMessages request succeeded</returns>
    public bool TryGetMessages(string SearchName, string SearchValue, string ComparisonOperation, ref List<EDXLDE> MessageList)
    {
      bool gotMessages = true;

      MessageList = null;

      try
      {
        MessageList = GetMessages(SearchName, SearchValue, ComparisonOperation);
      }
      catch
      {
        gotMessages = false;
      }

      return gotMessages;
    }

    /// <summary>
    /// GetMessages returns a list of ICNET EDXLDEs that satisfy the passed in DistributionId and
    /// SenderId.  Either the DistributionId or SenderId can be null/empty/whitespace but not both.
    /// </summary>
    /// <param name="DistributionId">DistributionId of the EDXLDE message.  Can be null/empty/whitespace.</param>
    /// <param name="SenderId">SenderId of the EDXLDE message.  Can be null/empty/whitespace.</param>
    /// <returns>A list of ICNET EDXLDEs that satisfy the passed in DistributionId and
    /// SenderId</returns>
    public List<EDXLDE> GetMessages(string DistributionId, string SenderId)
    {
      if (String.IsNullOrWhiteSpace(DistributionId) && String.IsNullOrWhiteSpace(SenderId))
      {
        throw new System.ArgumentNullException();
      }

      parameterListItem[] pListItems = GetParameterList(DistributionId, SenderId);
      EDXLDistribution[] edxlDists = null;
      List<EDXLDE> edxlDEs = null;

      try
      {
        edxlDists = mEDXLClient.getMessage(mEDXLHeader, REQUESTAPI, REQUESTMSG, pListItems);
        edxlDEs = new List<EDXLDE>();

        foreach (EDXLDistribution dist in edxlDists)
        {
          try
          {
            EDXLDE tmp = Convert_To_ICNET_EDXL(dist);

            if (tmp != null)
            {
              edxlDEs.Add(tmp);
            }
          }
          catch (Exception Ex)
          {
            log.Error("Error getting messages: " + Ex.ToString());
            throw Ex;
          }
        }

        return edxlDEs;
      }
      catch (Exception Ex)
      {
        log.Error("Error getting messages: " + Ex.ToString());
        throw Ex;
      }
    }

    /// <summary>
    /// GetMessages returns whether or not a GetMessage request for the passed-in DistributionId and
    /// SenderId succeeded.  Either the DistributionId or SenderId can be null/empty/whitespace but not both.
    /// </summary>
    /// <param name="DistributionId">DistributionId of the EDXLDE message.  Can be null/empty/whitespace.</param>
    /// <param name="SenderId">SenderId of the EDXLDE message.  Can be null/empty/whitespace.</param>
    /// <param name="Messages">A list of ICNET EDXLDEs that satisfy the passed in DistributionId and
    /// SenderId.</param>
    /// <returns>Whether or not a GetMessage request for the passed-in DistributionId and
    /// SenderId succeeded</returns>
    public bool TryGetMessages(string DistributionId, string SenderId, ref List<EDXLDE> Messages)
    {
      bool gotMessages = true;

      Messages = null;

      try
      {
        Messages = GetMessages(DistributionId, SenderId);
      }
      catch
      {
        gotMessages = false;
      }

      return gotMessages;
    }

    /// <summary>
    /// PostMessage posts the provided EDXLDE to the IPAWS server.
    /// </summary>
    /// <param name="DEObj">EDXLDE to post to IPAWS</param>
    public void PostMessage(EDXLDE DEObj)
    {
      EDXLDistribution ipawsDEObj = null;

      try
      {
        ipawsDEObj = Convert_To_IPAWS_EDXL(DEObj);
      }
      catch (Exception Ex)
      {
        log.Error("Unable to convert EDXLDE object to EDXLDistribution object: " + Ex.ToString());
        throw new System.ArgumentException("Unable to convert EDXLDE object to EDXLDistribution object");
      }

      try
      {
        string postReturn = mEDXLClient.postEdxlDe(mEDXLHeader, ipawsDEObj);
      }
      catch (Exception Ex)
      {
        log.Error("Error posting message: " + Ex.ToString());
        throw Ex;
      }
    }
    #endregion

    #region Protected Member Functions

    #endregion

    #region Private Member Functions
    /// <summary>
    /// InitializeClient creates the client connection to IPAWS, finds the appropriate IPAWS certificated based 
    /// on the CogId and CogUser used in this object's construction, and sets the client credentials to use
    /// that certificate.  Throws an error if no matching certificate can be found.
    /// </summary>
    private void InitializeClient()
    {
      bool foundCert = false;

      mEDXLClient = new DMOPEN_EDXLDEServiceClient();

      X509Store store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
      store.Open(OpenFlags.ReadOnly);

      string certSubject = "CN=" + mCogUser + "_" + mCogId.ToString();

      foreach (X509Certificate2 cert in store.Certificates)
      {
        if (cert.Subject.Contains(certSubject))
        {
          mEDXLClient.ClientCredentials.ClientCertificate.Certificate = cert;
          foundCert = true;
          break;
        }
      }

      if (!foundCert)
      {
        throw new System.ArgumentException("No matching certificate found in the personal store of the current user for the provide Cog Id and User");
      }
    }

    /// <summary>
    /// CreateEDXLHeader creates the IPAWS EDXL Header required for all messages.
    /// </summary>
    private void CreateEDXLHeader()
    {
      mEDXLHeader = new EdxlHeaderTypeDef();
      mEDXLHeader.logonUser = mCogUser;
      mEDXLHeader.logonCogId = mCogId;
      mEDXLHeader.logonCogIdSpecified = true;
    }

    /// <summary>
    /// Convert_To_ICNET_EDXL converts an IPAWS EDXLDistribution object into
    /// an ICNET EDXLDE object.
    /// </summary>
    /// <param name="DistObj">IPAWS EDXLDistribution object to transform</param>
    /// <returns>ICNET EDXLDE object</returns>
    private EDXLDE Convert_To_ICNET_EDXL(EDXLDistribution DistObj)
    {
      try
      {
        XmlSerializer distSerializer = new XmlSerializer(typeof(EDXLDistribution));
        StringWriter writer = new StringWriter();
        distSerializer.Serialize(writer, DistObj);

        XmlSerializer deSerializer = new XmlSerializer(typeof(EDXLDE));
        StringBuilder distXML = writer.GetStringBuilder();

        log.Debug(distXML.ToString());


        try
        {
            return new EDXLDE(distXML.ToString());
        }
        catch (Exception Ex)
        {
            log.Error("Error deserializing message: " + Ex.ToString());
            return null;
        }

      }
      catch (Exception)
      {
        throw;
      }
    }

    /// <summary>
    /// Convert_To_IPAWS_EDXL converts an ICNET EDXLDE object into
    /// an IPAWS EDXLDistribution object.
    /// </summary>
    /// <param name="DEObj">ICNET EDXLDE object to transform</param>
    /// <returns>IPAWS EDXLDistribution object</returns>
    private EDXLDistribution Convert_To_IPAWS_EDXL(EDXLDE DEObj)
    {
      XmlSerializer s = new XmlSerializer(typeof(EDXLDistribution));

      StringReader reader = new StringReader(DEObj.WriteToXML());

      EDXLDistribution temp = (EDXLDistribution)s.Deserialize(reader);

      AddDMCogAddress(temp);

      return temp;
    }

    /// <summary>
    /// AddDMCogAddress adds the required explicit address, for the provided 
    /// CogId during construction, to the EDXLDistribution object if
    /// it is missing.
    /// </summary>
    /// <param name="DistObj">EDXLDistribution object to add to</param>
    private void AddDMCogAddress(EDXLDistribution DistObj)
    {
      //add dmcog Explicit Address as required by IPAWS
      List<valueSchemeType> addressList;
      bool cogAddressExists = false;

      if (DistObj.explicitAddress != null)
      {
        addressList = DistObj.explicitAddress.ToList();

        foreach (valueSchemeType address in addressList)
        {
          if (address.explicitAddressScheme == "dmcog")
          {
            cogAddressExists = true;

            List<string> values = address.explicitAddressValue.ToList();

            if (!values.Contains(mCogId.ToString()))
            {
              values.Add(mCogId.ToString());
              address.explicitAddressValue = values.ToArray();
              break;
            }
          }
        }
      }
      else
      {
        addressList = new List<valueSchemeType>();
      }

      if (!cogAddressExists)
      {
        valueSchemeType cogAddress = new valueSchemeType();
        cogAddress.explicitAddressScheme = "dmcog";
        string[] cogs = new string[] { mCogId.ToString() };
        cogAddress.explicitAddressValue = cogs;
        addressList.Add(cogAddress);
      }

      DistObj.explicitAddress = addressList.ToArray();
    }

    /// <summary>
    /// GetParameterList creates the appropriate parameterListItem array based
    /// on the provided DistributionId and SenderId.
    /// </summary>
    /// <param name="DistributionId">DistributionId of the EDXLDE message.  Can be null/empty/whitespace.</param>
    /// <param name="SenderId">SenderId of the EDXLDE message.  Can be null/empty/whitespace.</param>
    /// <returns>ParameterListItem array based
    /// on the provided DistributionId and SenderId</returns>
    private parameterListItem[] GetParameterList(string DistributionId, string SenderId)
    {
      parameterListItem distributionIdItem = null;
      parameterListItem senderIdItem = null;

      List<parameterListItem> listItems = new List<parameterListItem>();

      if (!String.IsNullOrWhiteSpace(DistributionId))
      {
        distributionIdItem = new parameterListItem();
        distributionIdItem.parameterName = "distributionID";
        distributionIdItem.comparisonOp = "equalto";
        distributionIdItem.parameterValue = new string[] { DistributionId };

        listItems.Add(distributionIdItem);
      }

      if (!String.IsNullOrWhiteSpace(SenderId))
      {
        senderIdItem = new parameterListItem();
        senderIdItem.parameterName = "senderID";
        senderIdItem.comparisonOp = "equalto";
        senderIdItem.parameterValue = new string[] { SenderId };

        if (distributionIdItem != null)
        {
          distributionIdItem.logicalOp = "and";
        }

        listItems.Add(senderIdItem);
      }

      return listItems.ToArray();
    }
    #endregion
  }
}
